/*
 * $Id: Sttp.cc,v 1.5 2006-02-14 05:43:09 bacon Exp $
 */

#include <xpp/net/Sttp.h>
#include <xp/bas/utf8.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>

#define GET_CHAR          if (get_char() == -1) return -1;
#define GET_TOKEN         if (get_token() == -1) return -1;
#define GET_FIRST_TOKEN   if (get_token(true) == -1) return -1;
#define PUT_CHAR(x)       if (put_char(x) == -1) return -1;
#define PUT_ENC_CHAR(x)   if (SttpBase::put_char(x) == -1) return -1;

#define MARK_ENC XP_CHAR('!')

// TODO: the encryption portion must be modified.

namespace xpp
{
	namespace net
	{

		using namespace xpp::bas;

		Sttp::Sttp (Transmittable* s, Address* a): SttpBase (s, a)
		{
			reset ();	
		}

		Sttp::~Sttp ()
		{
		}
		
		void Sttp::reset ()
		{
			SttpBase::reset ();
			
			max_raw_cmd_len  = MAX_RAW_CMD_LEN;
			max_arg_count    = MAX_ARG_COUNT;
			raw_cmd_len      = 0;
		
			opt_enc_always   = false;	
			opt_enc_send     = false;
			opt_enc_receive  = false;
			opt_send_newline = true;

			rc4_receive.restart ();
			rc4_send.restart ();
		}

		int Sttp::addCmd (const xp_char_t* name, CmdProc proc)
		{
			return cmd_dict.putNew (name, proc);
		}
		
		int Sttp::removeCmd (const xp_char_t* name) 
		{
			return cmd_dict.remove (name);
		}

		void Sttp::removeAllCmds ()
		{
			cmd_dict.clear ();
		}
		
		int Sttp::receiveCmd (SttpCmd* cmd)
		{
			xp_assert (p_medium != XP_NULL);
		
			p_errcode = ERR_NONE;
		
			receive_cancel_requested = false;
			raw_cmd_len = 0;
			if (!opt_enc_always) opt_enc_receive = false;
		
			GET_CHAR;
			GET_FIRST_TOKEN;
		
			/*
			if (token_type == T_SEMICOLON) { 
				// null command
				cmd->clear ();
				return 0;
			}
			*/
		
			if (token_type == T_ENC) {
				// the two lines below are done inside get_token().
				// for a function call sequence problem.
				//opt_enc_receive = true;
				//rc4_receive.restart ();
				GET_TOKEN;
			}
		
			if (token_type != T_IDENT) {
				p_errcode = ERR_CMDNAME;
				return -1;
			}
		
			cmd->clear ();
			cmd->setName (token_value.buffer(), token_value.size());
		
			for (;;) {
				GET_TOKEN;
				if (token_type == T_STRING) {
					cmd->addArg (token_value.buffer(), token_value.size());
				}
				else if (token_type == T_IDENT) {
					// you don't have to quote a string owing to this.
					cmd->addArg (token_value.buffer(), token_value.size());
				}
				else {
					if (cmd->argCount() == 0) break;
					p_errcode = ERR_WRONGARG;
					return -1;
				}
		
				GET_TOKEN;
				if (token_type != T_COMMA) break;
				if (cmd->argCount() >= max_arg_count) {
					p_errcode = ERR_TOOMANYARGS;
					return -1;
				}
			}
		
			if (token_type != T_SEMICOLON) {
				p_errcode = ERR_SEMICOLON;
				return -1;
			}
		
			return 0;
		}
		
		int Sttp::processCmd (const SttpCmd& cmd)
		{
			p_errcode = ERR_NONE;
			if (cmd.isNullCmd()) return 0;
		
			CmdProc* proc = cmd_dict.get(cmd.name);
			if (proc == XP_NULL) {
				p_errcode = ERR_UNKNOWNCMD;
				return -1;
			}
			
			// preprocess is called only when the command is a valid command
			if (preprocess_cmd(cmd) == -1) return -1;

			// if this->**proc changes p_errcode, the value will
			// be just used unchanged. in case it doesn't change it,
			// ERR_CMDPROC becomes the default error code.
			p_errcode = ERR_CMDPROC; 
			if ((this->**proc) (cmd) == -1) {
				//p_errcode = ERR_CMDPROC;
				return -1;	
			}

			p_errcode = ERR_NONE;
			return postprocess_cmd (cmd);
		}
		
		int Sttp::sendCmd (const SttpCmd& cmd)
		{
			p_errcode = ERR_NONE;
		
			const xp_char_t* p = (const xp_char_t*)cmd.name;
			if (*p == XP_CHAR('\0')) return 0; // don't send a null command
		
			if (!opt_enc_always && opt_enc_send) {
				PUT_ENC_CHAR(MARK_ENC);	
				rc4_send.restart ();
			}
			while (*p != XP_CHAR('\0')) PUT_CHAR(*p++); 
		
			xp_size_t nargs = cmd.argCount();
			if (nargs > 0) {
				PUT_CHAR (XP_CHAR(' '));
		
				for (xp_size_t i = 0; i < nargs; i++) {
					const xp_char_t* arg = cmd.argAt(i);
					xp_size_t arg_len = cmd.argLenAt(i);
		
					PUT_CHAR (XP_CHAR('\"'));
					for (xp_size_t j = 0; j < arg_len; j++) {
						// Don't have to send a backslash when encryption is on
						// because only 16 characters from 'A' TO 'P' are used
						if (arg[j] == XP_CHAR('\\') || 
						    arg[j] == XP_CHAR('\"')) PUT_CHAR ('\\');
						PUT_CHAR (arg[j]);
					}
					PUT_CHAR (XP_CHAR('\"'));
		
					if (i < nargs - 1) PUT_CHAR (XP_CHAR(','));
				}
			}
		
			PUT_CHAR (XP_CHAR(';'));
			if (opt_send_newline) {
				PUT_CHAR (XP_CHAR('\r'));
				PUT_CHAR (XP_CHAR('\n'));
			}
			return flush_outbuf ();
		}
		
		int Sttp::sendCmd (const xp_char_t* name, xp_size_t nargs = 0, ...)
		{
			p_errcode = ERR_NONE;
		
			const xp_char_t* p = name;
			if (*p == XP_CHAR('\0')) return 0; // don't send a null command
		
			if (!opt_enc_always && opt_enc_send) {
				PUT_ENC_CHAR(MARK_ENC);	
				rc4_send.restart ();
			}
			while (*p != XP_CHAR('\0')) PUT_CHAR (*p++);
		
			if (nargs > 0) {
				xp_va_list ap;
				xp_va_start (ap, nargs);
		
				PUT_CHAR (XP_CHAR(' '));
				for (xp_size_t i = 1; i <= nargs; i++) {
					p = xp_va_arg (ap, xp_char_t*);
		
					PUT_CHAR (XP_CHAR('\"'));
					while (*p) {
						if (*p == XP_CHAR('\\') || 
						    *p == XP_CHAR('\"')) PUT_CHAR (XP_CHAR('\\'));
						PUT_CHAR (*p++); 
					}
					PUT_CHAR (XP_CHAR('\"'));
			
					if (i < nargs) PUT_CHAR (XP_CHAR(','));
				}
				xp_va_end (ap);
			}
		
			PUT_CHAR (XP_CHAR(';'));
			if (opt_send_newline) {
				PUT_CHAR (XP_CHAR('\r'));
				PUT_CHAR (XP_CHAR('\n'));
			}
			return flush_outbuf();
		}
		
		int Sttp::sendCmdL (const xp_char_t* name, xp_size_t nargs = 0, ...)
		{
			p_errcode = ERR_NONE;
		
			const xp_char_t* p = name;
			if (*p == XP_CHAR('\0')) return 0; // don't send a null command
		
			if (!opt_enc_always && opt_enc_send) {
				PUT_ENC_CHAR(MARK_ENC);	
				rc4_send.restart ();
			}
			while (*p != XP_CHAR('\0')) PUT_CHAR (*p++);
		
			if (nargs > 0) {
				xp_va_list ap;
				xp_va_start (ap, nargs);
		
				PUT_CHAR (XP_CHAR(' '));
				for (xp_size_t i = 1; i <= nargs; i++) {
					p   = xp_va_arg (ap, xp_char_t*);
					xp_size_t len = xp_va_arg (ap, xp_size_t);
		
					PUT_CHAR (XP_CHAR('\"'));
					while (len > 0) {
						if (*p == XP_CHAR('\\') ||
						    *p == XP_CHAR('\"')) PUT_CHAR (XP_CHAR('\\'));
						PUT_CHAR (*p++); 
						len--;
					}
					PUT_CHAR (XP_CHAR('\"'));
			
					if (i < nargs) PUT_CHAR (XP_CHAR(','));
				}
				xp_va_end (ap);
			}
		
			PUT_CHAR (XP_CHAR(';'));
			if (opt_send_newline) {
				PUT_CHAR (XP_CHAR('\r'));
				PUT_CHAR (XP_CHAR('\n'));
			}
			return flush_outbuf();
		}
		
		int Sttp::sendCmdL (
			const xp_char_t* name, xp_size_t nmlen, xp_size_t nargs = 0, ...)
		{
			p_errcode = ERR_NONE;
		
			xp_char_t* p = (xp_char_t*)name;
			if (*p == XP_CHAR('\0')) return 0; // don't send a null command
		
			if (!opt_enc_always && opt_enc_send) {
				PUT_ENC_CHAR(MARK_ENC);	
				rc4_send.restart ();
			}
			//while (*p != XP_CHAR('\0')) PUT_CHAR (*p++);
			while (nmlen > 0) {
				PUT_CHAR (*p++);
				nmlen--;
			}
		
			if (nargs > 0) {
				xp_va_list ap;
				xp_va_start (ap, nargs);
		
				PUT_CHAR (XP_CHAR(' '));
				for (xp_size_t i = 1; i <= nargs; i++) {
					p = xp_va_arg (ap, xp_char_t*);
					xp_size_t len = xp_va_arg (ap, xp_size_t);
		
					PUT_CHAR (XP_CHAR('\"'));
					while (len > 0) {
						if (*p == XP_CHAR('\\') || 
						    *p == XP_CHAR('\"')) PUT_CHAR (XP_CHAR('\\'));
						PUT_CHAR (*p++); 
						len--;
					}
					PUT_CHAR (XP_CHAR('\"'));
			
					if (i < nargs) PUT_CHAR (XP_CHAR(','));
				}
				xp_va_end (ap);
			}
		
			PUT_CHAR (XP_CHAR(';'));
			if (opt_send_newline) {
				PUT_CHAR (XP_CHAR('\r'));
				PUT_CHAR (XP_CHAR('\n'));
			}
			return flush_outbuf();
		}
		
		/*
		int Sttp::get_char (xp_cint_t* c)
		{
			xp_cint_t ch;
		
			if (SttpBase::get_char(&ch) == -1) {
				// NOTICE: p_errcode doesn't have to be set because 
				//         it is set in SttpBase::get_char().
				return -1;
			}
			if (opt_enc_receive) ch = rc4_receive.cipher (ch);

			if (raw_cmd_len >= max_raw_cmd_len) {
				p_errcode = ERR_TOOLONGCMD;
				return -1;
			}
			raw_cmd_len++;
		
			sttp_curc = ch;
			if (c != XP_NULL) *c = ch;
			return 0;
		}
		*/

		int Sttp::get_char (xp_cint_t* ch)
		{
			xp_size_t remain = 0;

			if (sttp_curp == inbuf_len) {
				xp_ssize_t n; 
				xp_time_t cnt;
		#ifdef XP_CHAR_IS_WCHAR
			get_char_utf8:
		#endif
				cnt = 0;
				p_medium->enableTimeout (1000);

				for (;;) {
					n = p_medium->receive (
						&inbuf[remain], xp_countof(inbuf) - remain);
					if (n != -1 || 
					    p_medium->errorCode() != Transmittable::ERR_TIMEDOUT) break;

					if (receive_cancel_requested || canCancelReceive()) {
						p_errcode = ERR_CANCEL;
						return -1;
					}
		
					if (receive_timeout != -1) {
						cnt += 1000;
					 	if (cnt >= receive_timeout) break;
					}
				}
		
				if (n == -1 && p_medium->errorCode() == Transmittable::ERR_TIMEDOUT) {
					p_errcode = ERR_TIMEDOUT;
					return -1;
				}
				if (n <= 0) {
					p_errcode = ERR_RECEIVE;
					return -1;
				}
		
				sttp_curp = 0;
				inbuf_len = (xp_size_t)n + remain;
			} 
		
		#ifdef XP_CHAR_IS_WCHAR
			if ((opt_enc_always || opt_enc_receive) && remain == 0) {
				// the first character is deciphered only once.
				inbuf[sttp_curp] = rc4_receive.cipher (inbuf[sttp_curp]);
			}

			xp_size_t seqlen = xp_utf8_sequence_len (inbuf[sttp_curp]);
			if (seqlen == 0) {
				sttp_curp++; // skip one byte
				p_errcode = ERR_UTF8_CONV;
				return -1;
			}

			remain = inbuf_len - sttp_curp;
			if (remain < seqlen) {
				// must read further...
				xp_memcpy (inbuf, &inbuf[sttp_curp], remain);
				sttp_curp = 0;
				inbuf_len = remain;
				goto get_char_utf8;
			}

			if (opt_enc_always || opt_enc_receive) {
				for (xp_size_t i = sttp_curp + 1; i < sttp_curp + seqlen; i++) {
					// only from the second character
					inbuf[i] = rc4_receive.cipher (inbuf[i]);
				}
			}

			xp_wchar_t wch;
			xp_size_t n = xp_utf8_to_unicode (&inbuf[sttp_curp], seqlen, &wch);
			if (n == 0) {
				sttp_curp++; // still must skip a character
				p_errcode = ERR_RECEIVE;
				return -1;	
			}

			sttp_curc = wch;
			sttp_curp += n;
		#else
			if (opt_enc_always || opt_enc_receive) 
				inbuf[sttp_curp] = rc4_receive.cipher (inbuf[sttp_curp]);
			sttp_curc = inbuf[sttp_curp++];
		#endif

			/*
			if (sttp_curc == XP_CHAR('\0')) {
				p_errcode = ERR_WRONGCHAR;
				return -1;
			}
			*/

			if (raw_cmd_len >= max_raw_cmd_len) {
				p_errcode = ERR_TOOLONGCMD;
				return -1;
			}
			raw_cmd_len++;

			if (ch != XP_NULL) *ch = sttp_curc;
			return 0;
		}
		
		int Sttp::get_token (bool first)
		{
			while (xp_isspace(sttp_curc)) GET_CHAR; // skip spaces...
		
			if (is_ident_char(sttp_curc)) return get_ident ();
			else if (sttp_curc == XP_CHAR('\"')) return get_string ();
			else if (sttp_curc == XP_CHAR(';')) {
				token_type = T_SEMICOLON;
				token_value = XP_CHAR(';');
				// do not read the next character to terminate a command
				// get_char ();
			}
			else if (sttp_curc == XP_CHAR(',')) {
				token_type  = T_COMMA;
				token_value = XP_CHAR(',');
				GET_CHAR;
			}
			else if (!opt_enc_always && sttp_curc == MARK_ENC) {
				token_type  = T_ENC;
				token_value = MARK_ENC;
				if (first) {
					opt_enc_receive = true;
					rc4_receive.restart ();
				}
				GET_CHAR;
			}
			else {
				p_errcode = ERR_WRONGCHAR;
				return -1;
			}
		
			return 0;
		}
		
		int Sttp::get_ident ()
		{
			token_type  = T_IDENT;
			token_value = XP_TEXT("");
		
			while (is_ident_char(sttp_curc)) {
				token_value.append (sttp_curc);
				GET_CHAR;
			}
		
			return 0;
		}
		
		int Sttp::get_string ()
		{
			bool escaped = false;
		
			token_type  = T_STRING;
			token_value = XP_TEXT("");
		
			GET_CHAR;
			for (;;) {
				if (escaped == true) {
					sttp_curc = translate_escaped_char (sttp_curc);
					escaped = false;
				}
				else {
					if (sttp_curc == XP_CHAR('\"')) {
						GET_CHAR;
						break;
					}
					else if (sttp_curc == XP_CHAR('\\')) {
						GET_CHAR;
						escaped = true;
						continue;
					}
				}

				token_value.append (sttp_curc);
				GET_CHAR;
			}
		
			return 0;
		}

		xp_cint_t Sttp::translate_escaped_char (xp_cint_t c)
		{
			if (c == XP_CHAR('n')) c = XP_CHAR('\n');
			else if (c == XP_CHAR('t')) c = XP_CHAR('\t');
			else if (c == XP_CHAR('r')) c = XP_CHAR('\r');
			else if (c == XP_CHAR('v')) c = XP_CHAR('\v');
			else if (c == XP_CHAR('f')) c = XP_CHAR('\f');
			else if (c == XP_CHAR('a')) c = XP_CHAR('\a');
			else if (c == XP_CHAR('b')) c = XP_CHAR('\b');
			//else if (c == XP_CHAR('0')) c = XP_CHAR('\0');

			return c;
		}
		
		bool Sttp::is_ident_char (xp_cint_t c)
		{
			return xp_isalnum(c) || c == XP_CHAR('_') || 
				c == XP_CHAR('.') || c == XP_CHAR('*') || c == XP_CHAR('@');
		}
		
		int Sttp::put_char (xp_cint_t c)
		{
		#ifdef XP_CHAR_IS_WCHAR
			xp_mchar_t buf[10];
			xp_size_t len = xp_unicode_to_utf8 (c, buf, xp_countof(buf));
			if (len == 0) {
				p_errcode = ERR_UTF8_CONV;
				return -1;
			}

			for (xp_size_t i = 0; i < len; i++) {
				if (opt_enc_always || opt_enc_send) buf[i] = rc4_send.cipher (buf[i]);
				if (SttpBase::put_mchar(buf[i]) == -1) return -1;
			}
			return 0;
		#else
			if (opt_enc_always || opt_enc_send) c = rc4_send.cipher (c);
			return SttpBase::put_char (c);
		#endif
		}

		int Sttp::preprocess_cmd (const SttpCmd& /*cmd*/)
		{
			return 0;
		}

		int Sttp::postprocess_cmd (const SttpCmd& /*cmd*/)
		{
			return 0;
		}

		const xp_char_t* Sttp::errorStr () const
		{
			switch (p_errcode) {
			case ERR_CMDNAME: 
				return XP_TEXT("command name expected");
			case ERR_CMDPROC: 
				return XP_TEXT("command procedure exit");
			case ERR_UNKNOWNCMD: 
				return XP_TEXT("unknown command");
			case ERR_TOOLONGCMD: 
				return XP_TEXT("command too long");
			case ERR_SEMICOLON: 
				return XP_TEXT("semicolon expected");
			case ERR_TOOMANYARGS: 
				return XP_TEXT("too many command arguments");
			case ERR_WRONGARG: 
				return XP_TEXT("wrong command argument");
			case ERR_WRONGCHAR: 
				return XP_TEXT("wrong character");
			default:
				return SttpBase::errorStr ();
			}
		}
		
	}
}
